package tk.pak0.audiomidifier.model.runnable;

import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;

import org.apache.log4j.Logger;

import com.petersalomonsen.jjack.javasound.TargetJJackLine;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import tk.pak0.audiomidifier.controller.Controller;
import tk.pak0.audiomidifier.model.persistence.A_DataHolder;
import tk.pak0.audiomidifier.model.persistence.XmlDataHolder;

public class SoundReader implements Runnable{
	
	private volatile boolean run=false;
	private static Logger log = Logger.getLogger(SoundReader.class);
	private Controller mainControl = Controller.getInstance();
	
	private PitchProcessor pitchThread;
	
	public void setPitchThread(PitchProcessor instance){
		this.pitchThread = instance;
	}

	public void run() throws RuntimeException {
		run = true;
		A_DataHolder dataHolder = XmlDataHolder.getInstance();
		
		int numBytesRead;
		int totalNumBytesRead = 0;
		
		TargetDataLine inLine = (TargetDataLine) mainControl.getLineaEntradaSel();
		AudioFormat format = mainControl.audioTools.getFormat();
		int bufferSizeInFrames = mainControl.audioTools.getBufferSizes()[dataHolder.bufferSize];
		String mixerName = mainControl.getSoundCardsNames()[dataHolder.soundCard];
		boolean usingJjack = mixerName.trim().toLowerCase().contains("jjack");
		
		int bufferSizeInBytes = bufferSizeInFrames * format.getFrameSize();
		if(!usingJjack){
			log.trace("Selected mixer: "+mixerName);
			log.trace("Buffersize in frames "+bufferSizeInFrames);
			log.trace("Buffer size in bytes "+bufferSizeInBytes);
		}
		byte[] data = new byte[bufferSizeInBytes / 4];
		
		try {
			if(usingJjack){
				inLine.open();
			}else{
				inLine.open(format, bufferSizeInFrames);
			}
		} catch (LineUnavailableException e) {
			log.fatal("Imposible abrir línea con los parámetros especificados", e);
		}
		if(inLine.isOpen()){
			if(usingJjack && log.isDebugEnabled()){
				log.debug("JJack seleccionado");
				AudioFormat jjackFormat = inLine.getFormat();
				log.debug("JJack SampleRate: "+jjackFormat.getSampleRate());			
				log.debug("JJack FrameSize: "+jjackFormat.getFrameSize());
				
				TargetJJackLine jjackLine = (TargetJJackLine) inLine;
				int sampleDepth = jjackFormat.getSampleSizeInBits();
				bufferSizeInBytes = (jjackLine.getBufferSize() / sampleDepth);
				bufferSizeInFrames = bufferSizeInBytes / jjackLine.getFormat().getFrameSize();
				log.trace("JJack Buffersize in frames "+bufferSizeInFrames);
				log.trace("JJack Buffersize in bytes "+bufferSizeInBytes);
				log.trace("JJack Buffersize sample depth "+sampleDepth);
				data = new byte[bufferSizeInBytes / 4];
			}
			inLine.flush();
			inLine.start();
			
			ByteOutputStream out = new ByteOutputStream(bufferSizeInBytes);
			
			while(run){
				// Drenamos la línea cuando se haya parado el hilo
				if(!run){
					inLine.drain();
				}
				// leer de la entrada de sonido
				log.debug("Leer de la entrada de sonido "+data.length+" bytes");
				numBytesRead =  inLine.read(data, 0, data.length);
				log.debug("Leídos "+numBytesRead+" bytes, escribiendo al buffer");
				out.write(data, 0, numBytesRead);
				totalNumBytesRead = totalNumBytesRead + numBytesRead;
				// cargar trozo
				log.debug("Cargar trozo");
				if(totalNumBytesRead >= bufferSizeInBytes){
					// enviar frame
					log.debug("Frame completo, enviando.");
					if(log.isTraceEnabled()){
						totalNumBytesRead = 0;
						byte[] storedData = out.getBytes();
						try {
							out.flush();
							pitchThread.putChunkInQueue(storedData);
						} catch (InterruptedException e) {
							log.error("Poner en cola interrumpido ",e);
							e.printStackTrace();
						} catch (IOException e){
							log.error("Error al hacer flush en el stream ",e);
							e.printStackTrace();
						}
					}
				}
			}
			out.close();
		}else{
			throw new RuntimeException("Selected line is not open");
		}
		
		// Cerramos la  línea de entrada
		if(inLine.isOpen()){
			inLine.close();
		}
	}
	
	public void kill(){
		run = false;
	}
}
